package com.ddwl.funds.service;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ddwl.common.constant.BaseEnum;
import com.ddwl.common.constant.OrderEnum;
import com.ddwl.common.constant.OrderTypeEnum;
import com.ddwl.common.constant.PaymentEnum;
import com.ddwl.common.exception.ParamException;
import com.ddwl.common.feign.api.GateApi;
import com.ddwl.common.util.SignUtil;
import com.ddwl.common.util.UUIDGenerator;
import com.ddwl.funds.constant.FundsConstant;
import com.ddwl.funds.dao.ByteDancePayDao;
import com.ddwl.funds.dao.ByteDanceRefundDao;
import com.ddwl.funds.dao.OrderRefundsDao;
import com.ddwl.funds.dao.model.ByteDancePay;
import com.ddwl.funds.dao.model.ByteDanceRefund;
import com.ddwl.funds.dao.model.OrderPay;
import com.ddwl.funds.dao.model.OrderRefunds;
import com.ddwl.schema.bo.funds.ByteDancePayNotifyBo;
import com.ddwl.schema.bo.funds.ByteDanceRefundNotifyBo;
import com.ddwl.schema.bo.gate.byteDance.ByteOrderBo;
import com.ddwl.schema.bo.gate.byteDance.ByteRefundBo;
import com.ddwl.schema.vo.funds.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * @Author hemiaojun
 * @Date 2021/8/4 16:06
 */
@Slf4j
@RefreshScope
@Service
public class ByteDanceServer {

    @Resource
    private GateApi gateApi;
    @Resource
    private ByteDancePayDao byteDancePayDao;
    @Resource
    private ByteDanceRefundDao byteDanceRefundDao;
    @Resource
    private OrderRefundsDao orderRefundsDao;
    @Resource
    private  PaymentServer paymentServer;
    @Resource
    private RabbitTemplate rabbitTemplate;

    @Value("${spring.profiles.active}")
    private String active;

    @Value("${ddwl.test.payamount}")
    private String testPayAmount;

    @Value("${ddwl.byteDancePay.salt}")
    private String salt;

    @Value("${ddwl.byteDancePay.notify_url}")
    private String payNotifyUrl;

    @Value("${ddwl.byteDancePay.refund_notify_url}")
    private String refundNotifyUrl;

    private String success = "SUCCESS";

    @Transactional(rollbackFor = Exception.class)
    public ByteDanceOrderVo createPayOrder(OrderPay orderPay) {
        ByteDancePay exist = queryOrder(orderPay.getOrderId());
        ParamException.isTrue(null != exist && OrderEnum.PayStatus.SUCCEED.getCode().equals(exist.getStatus()), "订单已经支付成功，不要重复支付");
        ByteDancePay byteDancePay = new ByteDancePay();
        byteDancePay.setId(UUIDGenerator.getShortUUID());
        byteDancePay.setAppId(BaseEnum.ByteDanceAppId.MICRO_APP.getCode());
        byteDancePay.setOutOrderNo(orderPay.getOrderId());
        byteDancePay.setTotalAmount((orderPay.getAmount().multiply(new BigDecimal(100))).intValue());
        byteDancePay.setStatus(OrderEnum.PayStatus.PRE.getCode());
        byteDancePay.setSubject(OrderEnum.WX_ORDER_BODY);
        byteDancePay.setBody(OrderEnum.WX_ORDER_BODY);
        byteDancePay.setValidTime(24 * 3600 * 3600);
        byteDancePay.setNotifyUrl(payNotifyUrl);

        ByteOrderBo bo = new ByteOrderBo();
        bo.setApp_id(BaseEnum.ByteDanceAppId.MICRO_APP.getCode());
        bo.setOut_order_no(byteDancePay.getId());

        Map<String, String> map = new HashMap<>(16);
        map.put("app_id", BaseEnum.ByteDanceAppId.MICRO_APP.getCode());
        map.put("out_order_no", byteDancePay.getId());
        if (FundsConstant.test_profile.contains(active) && !OrderTypeEnum.moreCredit.getCode().equals(orderPay.getOrderType())) {
            map.put("total_amount", String.valueOf(new BigDecimal(testPayAmount).multiply(new BigDecimal(100)).intValue()));
            bo.setTotal_amount(new BigDecimal(testPayAmount).multiply(new BigDecimal(100)).intValue());
        }else{
            map.put("total_amount", byteDancePay.getTotalAmount().toString());
            bo.setTotal_amount(byteDancePay.getTotalAmount());
        }
        map.put("subject", byteDancePay.getSubject());
        map.put("body", byteDancePay.getBody());
        map.put("valid_time", byteDancePay.getValidTime().toString());
        map.put("notify_url", byteDancePay.getNotifyUrl());
        bo.setSubject(byteDancePay.getSubject());
        bo.setBody(byteDancePay.getBody());
        bo.setValid_time(byteDancePay.getValidTime());
        bo.setNotify_url(byteDancePay.getNotifyUrl());
        String sign = sign(map);
        bo.setSign(sign);
        byteDancePay.setSign(sign);
        log.info("byteDance createOrder params:{}",JSON.toJSONString(bo));
        ByteDanceOrderVo vo = gateApi.createOrder(bo).getResult();
        log.info("byteDance payOrder return:{}",JSON.toJSONString(vo));
        byteDancePay.setErrNo(vo.getErr_no());
        byteDancePay.setErrTips(vo.getErr_tips());
        if (null != vo.getData()) {
            byteDancePay.setOrderId(vo.getData().getOrder_id());
            byteDancePay.setData(JSON.toJSONString(vo.getData()));
        }
        byteDancePayDao.insert(byteDancePay);
        return vo;
    }

    public ByteDancePay queryOrder(String orderPayId){
        ByteDancePay entity = byteDancePayDao.getByOutOrderNo(orderPayId);
        if (null != entity) {
            if (OrderEnum.PayStatus.SUCCEED.getCode().equals(entity.getStatus())) {
                return entity;
            }
            entity = queryOrderStatus(entity.getId());
        }
        return entity;
    }

    public ByteDancePay queryOrderStatus(String outOrderNo) {
        ByteDancePay entity = byteDancePayDao.selectByPrimaryKey(outOrderNo);
        ParamException.isNull(entity,"订单不存在");
        Map<String, String> map = new HashMap<>(16);
        map.put("app_id", BaseEnum.ByteDanceAppId.MICRO_APP.getCode());
        map.put("out_order_no", outOrderNo);
        String sign = sign(map);

        ByteOrderBo bo = new ByteOrderBo();
        bo.setApp_id(BaseEnum.ByteDanceAppId.MICRO_APP.getCode());
        bo.setOut_order_no(outOrderNo);
        bo.setSign(sign);

        log.info("byteDance queryOrder params:{}",JSON.toJSONString(bo));
        ByteDanceQueryOrderVo vo = gateApi.queryOrder(bo).getResult();
        log.info("byteDance queryOrder result:{}",JSON.toJSONString(vo));

        ByteDancePay update = new ByteDancePay();
        update.setId(outOrderNo);
        update.setErrNo(vo.getErr_no());
        update.setErrTips(vo.getErr_tips());
        update.setOrderId(vo.getOrder_id());
        update.setPaymentInfo(JSON.toJSONString(vo.getPayment_info()));
        if (success.equals(vo.getPayment_info().getOrder_status())) {
            update.setStatus(OrderEnum.PayStatus.SUCCEED.getCode());
            paymentServer.completePayment(entity.getAppId(),entity.getOutOrderNo(),update.getOrderId(),PaymentEnum.BYTE_DANCE.getCode(),entity.getId());
        }
        byteDancePayDao.updateByPrimaryKeySelective(update);
        return update;
    }

    public String byteDancePayNotify(String notifyStr) {
        log.info("===========notifyStr:{}",notifyStr);
        ByteDancePayNotifyBo bo = JSON.parseObject(notifyStr, ByteDancePayNotifyBo.class);
        log.info("===========收到字节支付回调 ByteDancePayNotifyBo={}",JSON.toJSONString(bo));
        JSONObject jsonObject = JSON.parseObject(notifyStr);
        JSONObject msg = jsonObject.getJSONObject("msg");
        log.info("msg:{}",msg);
        ByteDanceNotifyVo vo = new ByteDanceNotifyVo();
        if (success.equals(msg.getString("status"))) {
            ByteDancePay byteDancePay = byteDancePayDao.selectByPrimaryKey(msg.getString("cp_orderno"));
            ParamException.isNull(byteDancePay,"订单不存在");
            ByteDancePay update = new ByteDancePay();
            update.setId(byteDancePay.getId());
            update.setTimestamp(bo.getTimestamp());
            update.setNonce(bo.getNonce());
            update.setMsg(JSON.toJSONString(bo.getMsg()));
            update.setType(bo.getType());
            update.setMsgSignature(bo.getMsg_signature());
            update.setStatus(OrderEnum.PayStatus.SUCCEED.getCode());
            byteDancePayDao.updateByPrimaryKeySelective(update);
            vo.setErr_no(0);
            vo.setErr_tips("success");
            paymentServer.completePayment(byteDancePay.getAppId(),byteDancePay.getOutOrderNo(),byteDancePay.getOrderId(), PaymentEnum.BYTE_DANCE.getCode(), byteDancePay.getId());
        }else{
            vo.setErr_no(1);
            vo.setErr_tips("fail");
        }
        return JSON.toJSONString(vo);
    }

    @Async
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.NOT_SUPPORTED)
    public ByteDanceRefund refunds(OrderRefunds orderRefunds) {
        ByteDancePay byteDancePay = byteDancePayDao.getByOutOrderNo(orderRefunds.getOrderNo());
        ParamException.isNull(byteDancePay, "订单不存在");

        ByteDanceRefund byteDanceRefund = new ByteDanceRefund();
        byteDanceRefund.setId(UUIDGenerator.getShortUUID());
        byteDanceRefund.setAppId(BaseEnum.ByteDanceAppId.MICRO_APP.getCode());
        byteDanceRefund.setOutOrderNo(byteDancePay.getId());
        byteDanceRefund.setOutRefundNo(orderRefunds.getId());
        byteDanceRefund.setReason("售后退款");
        if (FundsConstant.test_profile.contains(active) && !OrderTypeEnum.moreCredit.getCode().equals(orderRefunds.getOrderType())) {
            byteDanceRefund.setTotalAmount(new BigDecimal(testPayAmount).multiply(new BigDecimal("100")).intValue());
            byteDanceRefund.setRefundAmount(1);
        }else{
            byteDanceRefund.setTotalAmount(orderRefunds.getOrderAmount().multiply(new BigDecimal("100")).intValue());
            byteDanceRefund.setRefundAmount(orderRefunds.getRefundsAmount().multiply(new BigDecimal("100")).intValue());
        }
        byteDanceRefund.setNotifyUrl(refundNotifyUrl);

        String sign = getRefundSign(byteDanceRefund);
        ByteRefundBo bo = createRefundBo(byteDanceRefund, sign);
        byteDanceRefund.setSign(sign);

        log.info("byteDance refunds params:{}",JSON.toJSONString(bo));
        ByteDanceRefundVo vo = gateApi.createRefund(bo).getResult();
        log.info("byteDance refunds result:{}",JSON.toJSONString(vo));

        if (Integer.valueOf(0).equals(vo.getErr_no())) {
            byteDanceRefund.setStatus(OrderEnum.PayStatus.SUCCEED.getCode());
            orderRefunds.setTransactionId(vo.getRefund_no());
        }else{
            byteDanceRefund.setStatus(OrderEnum.PayStatus.FILL.getCode());
            orderRefunds.setErrMsg(vo.getErr_tips());
            orderRefunds.setRefundsFlowId(byteDanceRefund.getId());
            orderRefunds.setStatus(OrderEnum.PayStatus.FILL.getCode());
        }
        byteDanceRefund.setErrNo(vo.getErr_no());
        byteDanceRefund.setErrTips(vo.getErr_tips());
        byteDanceRefund.setRefundNo(vo.getRefund_no());
        byteDanceRefundDao.insert(byteDanceRefund);
        if (StrUtil.isNotBlank(orderRefunds.getNotifyMethod())) {
            rabbitTemplate.convertAndSend(orderRefunds.getNotifyMethod(), JSON.toJSONString(orderRefunds));
        }
        return byteDanceRefund;
    }

    private ByteRefundBo createRefundBo(ByteDanceRefund byteDanceRefund, String sign) {
        ByteRefundBo bo = new ByteRefundBo();
        bo.setApp_id(BaseEnum.ByteDanceAppId.MICRO_APP.getCode());
        bo.setOut_order_no(byteDanceRefund.getOutOrderNo());
        bo.setOut_refund_no(byteDanceRefund.getOutRefundNo());
        bo.setReason(byteDanceRefund.getReason());
        bo.setTotal_amount(byteDanceRefund.getTotalAmount());
        bo.setRefund_amount(byteDanceRefund.getRefundAmount());
        bo.setNotify_url(byteDanceRefund.getNotifyUrl());
        bo.setSign(sign);
        return bo;
    }

    private String getRefundSign(ByteDanceRefund byteDanceRefund) {
        Map<String, String> map = new HashMap<>(16);
        map.put("app_id", BaseEnum.ByteDanceAppId.MICRO_APP.getCode());
        map.put("out_order_no", byteDanceRefund.getOutOrderNo());
        map.put("out_refund_no", byteDanceRefund.getOutRefundNo());
        map.put("reason", byteDanceRefund.getReason());
        map.put("total_amount", byteDanceRefund.getTotalAmount().toString());
        map.put("refund_amount", byteDanceRefund.getRefundAmount().toString());
        map.put("notify_url", byteDanceRefund.getNotifyUrl());
        String sign = sign(map);
        return sign;
    }

    public String byteDanceRefundNotify(String notifyStr) {

        log.info("===========refundNotifyStr:{}",notifyStr);
        ByteDanceRefundNotifyBo bo = JSON.parseObject(notifyStr, ByteDanceRefundNotifyBo.class);
        log.info("===========收到字节退款回调 ByteDanceRefundNotifyBo={}", JSON.toJSONString(bo));
        JSONObject jsonObject = JSON.parseObject(notifyStr);
        JSONObject msg = jsonObject.getJSONObject("msg");
        log.info("msg:{}",msg);

        ByteDanceNotifyVo vo = new ByteDanceNotifyVo();
        if (success.equals(msg.getString("status"))) {
            ByteDanceRefund exist = byteDanceRefundDao.getByOutRefundNo(msg.getString("cp_refundno"));
            ParamException.isNull(exist,"未找到支付订单");
            ByteDanceRefund update = new ByteDanceRefund();
            update.setId(exist.getId());
            update.setTimestamp(bo.getTimestamp());
            update.setNonce(bo.getNonce());
            update.setType(bo.getType());
            update.setMsgSignature(bo.getMsg_signature());
            update.setMsg(JSON.toJSONString(bo.getMsg()));
            update.setStatus(OrderEnum.PayStatus.SUCCEED.getCode());
            byteDanceRefundDao.updateByPrimaryKeySelective(update);
            vo.setErr_no(0);
            vo.setErr_tips("success");
            paymentServer.completeRefunds(exist.getOutRefundNo(),exist.getRefundNo(),exist.getId());
        }else{
            vo.setErr_no(1);
            vo.setErr_tips("fail");
        }
        return JSON.toJSONString(vo);
    }

    public void queryRefund(OrderRefunds orderRefunds) {
        ByteDanceRefund byteDanceRefund = byteDanceRefundDao.selectByPrimaryKey(orderRefunds.getRefundsFlowId());
        if (byteDanceRefund == null) {
            refunds(orderRefunds);
            return;
        }
        orderRefunds.setRefundsFlowId(byteDanceRefund.getId());
        orderRefunds.setStatus(OrderEnum.PayStatus.SUCCEED.getCode());
        orderRefunds.setTransactionId(byteDanceRefund.getRefundNo());
        if (!OrderEnum.PayStatus.SUCCEED.getCode().equals(byteDanceRefund.getStatus())) {
            ByteDanceRefund update = new ByteDanceRefund();
            update.setId(byteDanceRefund.getId());

            Map<String, String> map = new HashMap<>(16);
            map.put("app_id", BaseEnum.ByteDanceAppId.MICRO_APP.getCode());
            map.put("out_refund_no", byteDanceRefund.getOutRefundNo());
            String sign = sign(map);
            ByteRefundBo bo = new ByteRefundBo();
            bo.setApp_id(BaseEnum.ByteDanceAppId.MICRO_APP.getCode());
            bo.setOut_refund_no(byteDanceRefund.getOutRefundNo());
            bo.setSign(sign);

            log.info("byteDance queryRefund params:{}", JSON.toJSONString(bo));
            ByteDanceQueryRefundVo vo = gateApi.queryRefund(bo).getResult();
            log.info("byteDance queryRefund result:{}", JSON.toJSONString(vo));

            update.setErrNo(vo.getErr_no());
            update.setErrTips(vo.getErr_tips());
            update.setRefundInfo(JSON.toJSONString(vo.getRefundInfo()));
            if (Integer.valueOf(0).equals(vo.getErr_no()) && success.equals(vo.getRefundInfo().getRefund_status())) {
                update.setStatus(OrderEnum.PayStatus.SUCCEED.getCode());
                update.setRefundNo(vo.getRefundInfo().getRefund_no());
            }else{
                orderRefunds.setStatus(OrderEnum.PayStatus.FILL.getCode());
                orderRefunds.setErrMsg(vo.getErr_tips());
            }
            byteDanceRefundDao.updateByPrimaryKeySelective(update);
        }
        orderRefundsDao.updateByPrimaryKeySelective(orderRefunds);
        if (StrUtil.isNotBlank(orderRefunds.getNotifyMethod())) {
            rabbitTemplate.convertAndSend(orderRefunds.getNotifyMethod(), JSON.toJSONString(orderRefunds));
        }
    }

    private String sign(Map<String,String> map){
        List<String> list = new ArrayList<>();
        list.add("app_id");
        list.add("thirdparty_id");
        list.add("sign");
        map.put("SALT", salt);
        String sign = SignUtil.generateSignature(map, list);
        return sign;
    }

}



